﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Drawing;
using System.Globalization;

namespace Autoverwaltung.Datenhaltung
{
    public class Datenhaltung1 : IDatenhaltung
    {
        private AutoverwaltungModelCollection<Model.Hersteller> hersteller;
        private AutoverwaltungModelCollection<Model.Modell> modelle;
        private AutoverwaltungModelCollection<Model.Auto> autos;
        
        private static readonly string PATH = AppDomain.CurrentDomain.BaseDirectory + @"\Daten\";
        private static readonly string FILENAME = "Datenhaltung.xml";
        private static readonly string FULL_PATH = PATH + FILENAME;

        private readonly XmlDocument document = new XmlDocument();

        /// <summary>
        /// Erstellt eine neue Instanz der
        /// Datenhaltung1-Klasse, überprüft
        /// ob die benötigte Datei
        /// "Datenhaltung1.xml" vorhanden ist 
        /// und ob sie bearbeitet werden kann.
        /// Wurde die Datei nicht gefunden wird
        /// versucht sie anzulegen.
        /// Exceptions die auftreten können sind
        /// hier gelistet <see cref="System.Xml.Document.Load()"/>.
        /// </summary>
        public Datenhaltung1()
        {
            try
            {
                this.LoadXml();
            }
            catch(Exception ex)
            {
                if (ex is DirectoryNotFoundException)
                {
                    Directory.CreateDirectory(PATH);
                }

                try
                {
                    XmlNode root = this.CreateNode("autoverwaltung");
                    root.AppendChild(this.CreateNode("herstellerroot"));
                    root.AppendChild(this.CreateNode("modelle"));
                    root.AppendChild(this.CreateNode("kraftstoffe"));
                    root.AppendChild(this.CreateNode("autos"));
                    this.document.AppendChild(root);
                    this.document.Save(FULL_PATH);

                    this.LoadData();
                }
                catch
                {
                    throw new SystemException("Datenhaltung1 konnte die Datei '" + FILENAME + "' weder lesen, finden noch erstellen!");
                }
            }
        }

        #region Load from xml
        private void LoadXml()
        {
            this.document.Load(FULL_PATH);
            this.LoadData();
        }

        private void LoadData()
        {
            this.LoadHersteller();
            this.LoadModelle();
            this.LoadAutos();
        }

        #region Lade hersteller
        private Model.Hersteller[] LoadHersteller()
        {
            this.hersteller = new AutoverwaltungModelCollection<Model.Hersteller>();

            XmlNode herstellerRootNode = this.GetNodeByName("herstellerroot");
            if (herstellerRootNode != null)
            {
                foreach (XmlNode herstellerNode in herstellerRootNode.ChildNodes)
                {
                    Model.Hersteller tHersteller = this.LoadHersteller(herstellerNode);
                    if (tHersteller != null)
                    {
                        this.hersteller.Add(tHersteller);
                    }
                }
            }

            return this.hersteller.ToArray();
        }

        private Model.Hersteller LoadHersteller(XmlNode herstellerNode)
        {
            string name = GetAttributeValue(herstellerNode, "name");
            int herstellerId = (int)Helper.StringToDecimal(GetAttributeValue(herstellerNode, "id"));

            if (name != null)
            {
                Model.Hersteller hersteller = new Model.Hersteller() { Id = herstellerId, Name = name };
                this.hersteller.Add(hersteller);
                return hersteller;
            }

            return null;
        }
        #endregion

        #region Lade Modelle
        private Model.Modell[] LoadModelle()
        {
            this.modelle = new AutoverwaltungModelCollection<Model.Modell>();

            XmlNode modellRootNode = this.GetNodeByName("modelle");
            if (modellRootNode != null)
            {
                foreach (XmlNode modellNode in modellRootNode.ChildNodes)
                {
                    Model.Modell tModell = this.LoadModell(modellNode);
                    if (tModell != null)
                    {
                        this.modelle.Add(tModell);
                    }
                }
            }

            return this.modelle.ToArray();
        }

        private Model.Modell LoadModell(XmlNode modellNode)
        {
            string bezeichnung = GetAttributeValue(modellNode, "bezeichnung");
            int modellId = (int)Helper.StringToDecimal(GetAttributeValue(modellNode, "id", "-1"));
            int herstellerId = (int)Helper.StringToDecimal(GetAttributeValue(modellNode, "hersteller-id", "-1"));

            Model.Hersteller hersteller = null;
            if (this.hersteller != null)
            {
                hersteller = this.hersteller.GetById(herstellerId);
            }

            Model.Modell modell = new Model.Modell() { Id = modellId, Bezeichnung = bezeichnung, Hersteller = hersteller };
            this.modelle.Add(modell);
            return modell;
        }
        #endregion

        #region Load Autos
        private Model.Auto[] LoadAutos()
        {
            this.autos = new AutoverwaltungModelCollection<Model.Auto>();

            XmlNode autoRootNode = this.GetNodeByName("autos");
            if (autoRootNode != null)
            {
                foreach (XmlNode autoNode in autoRootNode.ChildNodes)
                {
                    Model.Auto tAuto = this.LoadAuto(autoNode);
                    if (tAuto != null)
                    {
                        this.autos.Add(tAuto);
                    }
                }
            }

            return this.autos.ToArray();
        }

        private Model.Auto LoadAuto(XmlNode autoNode)
        {
            int autoId = (int)Helper.StringToDecimal(GetAttributeValue(autoNode, "id", "-1"));
            int modellId = (int)Helper.StringToDecimal(GetAttributeValue(autoNode, "modell-id", "-1"));
            string kraftstoff = GetAttributeValue(autoNode, "kraftstoff");
            int leistung = (int)Helper.StringToDecimal(GetAttributeValue(autoNode, "leistung"));
            int hubraum = (int)Helper.StringToDecimal(GetAttributeValue(autoNode, "hubraum"));
            int zylinder = (int)Helper.StringToDecimal(GetAttributeValue(autoNode, "zylinder"));
            decimal verbrauch_innerorts = Helper.StringToDecimal(GetAttributeValue(autoNode, "verbrauch-innerorts"));
            decimal verbrauch_ausserorts = Helper.StringToDecimal(GetAttributeValue(autoNode, "verbrauch-ausserorts"));
            Color farbe = Helper.HexToColor(GetAttributeValue(autoNode, "farbe"));

            Model.Verbrauch verbrauch = new Model.Verbrauch() { Innerorts = verbrauch_innerorts, Ausserorts = verbrauch_ausserorts };

            Model.Modell modell = null;
            if (this.modelle != null)
            {
                modell = this.modelle.GetById(modellId);
            }

            Model.Auto auto = new Model.Auto()
            {
                Id = autoId,
                Modell = modell,
                Kraftstoff = kraftstoff,
                Leistung = leistung,
                Hubraum = hubraum,
                Zylinder = zylinder,
                Verbrauch = verbrauch,
                Farbe = farbe
            };
            this.autos.Add(auto);
            return auto;
        }
        #endregion
        #endregion

        #region Create node for model
        private XmlNode CreateHerstellerNode(Model.Hersteller hersteller)
        {
            if (hersteller != null)
            {
                XmlNode herstellerNode = this.CreateNode("hersteller");
                herstellerNode.Attributes.Append(this.CreateAttribute("id", hersteller.Id.ToString(CultureInfo.InvariantCulture)));
                herstellerNode.Attributes.Append(this.CreateAttribute("name", hersteller.Name));

                return herstellerNode;
            }

            return null;
        }

        private XmlNode CreateModellNode(Model.Modell modell)
        {
            if (modell != null)
            {
                XmlNode modellNode = this.CreateNode("modell");
                modellNode.Attributes.Append(this.CreateAttribute("id", modell.Id.ToString(CultureInfo.InvariantCulture)));
                if (modell.Hersteller != null)
                {
                    modellNode.Attributes.Append(this.CreateAttribute("hersteller-id", modell.Hersteller.Id.ToString(CultureInfo.InvariantCulture)));
                }
                modellNode.Attributes.Append(this.CreateAttribute("bezeichnung", modell.Bezeichnung));

                return modellNode;
            }

            return null;
        }

        private XmlNode CreateAutoNode(Model.Auto auto)
        {
            if (auto != null)
            {
                XmlNode autoNode = this.CreateNode("auto");
                autoNode.Attributes.Append(this.CreateAttribute("id", auto.Id.ToString(CultureInfo.InvariantCulture)));
                if (auto.Modell != null)
                {
                    autoNode.Attributes.Append(this.CreateAttribute("modell-id", auto.Modell.Id.ToString(CultureInfo.InvariantCulture)));
                }
                if (auto.Kraftstoff != null)
                {
                    autoNode.Attributes.Append(this.CreateAttribute("kraftstoff", auto.Kraftstoff));
                }
                autoNode.Attributes.Append(this.CreateAttribute("leistung", auto.Leistung.ToString(CultureInfo.InvariantCulture)));
                autoNode.Attributes.Append(this.CreateAttribute("hubraum", auto.Hubraum.ToString(CultureInfo.InvariantCulture)));
                autoNode.Attributes.Append(this.CreateAttribute("zylinder", auto.Zylinder.ToString(CultureInfo.InvariantCulture)));
                if (auto.Verbrauch != null)
                {
                    autoNode.Attributes.Append(this.CreateAttribute("verbrauch-innerorts", auto.Verbrauch.Innerorts.ToString(CultureInfo.InvariantCulture)));
                    autoNode.Attributes.Append(this.CreateAttribute("verbrauch-ausserorts", auto.Verbrauch.Ausserorts.ToString(CultureInfo.InvariantCulture)));
                }
                if (auto.Farbe != null)
                {
                    autoNode.Attributes.Append(this.CreateAttribute("farbe", ColorTranslator.ToHtml(auto.Farbe)));
                }

                return autoNode;
            }

            return null;
        }
        #endregion

        #region Neu
        private bool Neu(string rootNodeName, XmlNode newNode)
        {
            XmlNode rootNode = this.GetNodeByName(rootNodeName);
            if (rootNode != null && newNode != null)
            {
                try
                {
                    rootNode.AppendChild(newNode);
                    return this.Speichern();
                }
                catch { }
            }

            return false;
        }
        #endregion

        #region Bearbeiten
        private XmlNode GetNodeById(string rootNodeName, int id)
        {
            if (rootNodeName != null)
            {
                XmlNode rootNode = this.GetNodeByName(rootNodeName);
                if (rootNode != null)
                {
                    foreach (XmlNode node in rootNode.ChildNodes)
                    {
                        int tmpId = (int)Helper.StringToDecimal(GetAttributeValue(node, "id"));

                        if (tmpId.Equals(id))
                        {
                            return node;
                        }
                    }
                }
            }

            return null;
        }
        #endregion

        #region Loeschen
        private bool Loesche(string rootNodeName, int id)
        {
            if (rootNodeName != null)
            {
                XmlNode rootNode = this.GetNodeByName(rootNodeName);
                if (rootNode != null)
                {
                    XmlNode deleteNode = this.GetNodeById(rootNodeName, id);

                    if (deleteNode != null)
                    {
                        rootNode.RemoveChild(deleteNode);
                        return this.Speichern();
                    }
                }
            }

            return false;
        }
        #endregion

        #region Speichern
        private bool Speichern()
        {
            try
            {
                this.document.Save(FULL_PATH);

                this.LoadData();

                return true;
            }
            catch { }

            return false;
        }
        #endregion

        #region Xml helper
        private XmlNode CreateNode(string name)
        {
            return this.document.CreateElement(name);
        }

        private XmlNode GetNodeByName(string nodeName)
        {
            if (nodeName == null)
            {
                return null;
            }

            XmlNodeList nodes = this.document.GetElementsByTagName(nodeName);
            if (nodes.Count >= 1)
            {
                return nodes[0];
            }
            return null;
        }

        private XmlAttribute CreateAttribute(string name, object value)
        {
            XmlAttribute attribute = this.document.CreateAttribute(name);
            attribute.Value = value.ToString();
            return attribute;
        }

        private static string GetAttributeValue(XmlNode node, string attributeName, string attributeNotFoundReturnValue = null)
        {
            XmlAttribute attribute = node.Attributes[attributeName];

            if(attribute != null)
            {
                return attribute.Value;
            }

            return attributeNotFoundReturnValue;
        }
        #endregion

        #region Implementierung IDatanhaltung
        #region Laden
        public Model.Hersteller[] LadeHersteller()
        {
            return this.hersteller.ToArray();
        }

        public Model.Modell[] LadeModelle()
        {
            return this.modelle.ToArray();
        }

        public Model.Auto[] LadeAutos()
        {
            return this.autos.ToArray();
        }

        #endregion

        #region Neu
        public bool NeuerHersteller(Model.Hersteller hersteller)
        {
            if (hersteller != null)
            {
                hersteller.Id = this.hersteller.NextId;
                return this.Neu("herstellerroot", this.CreateHerstellerNode(hersteller));
            }

            return false;
        }

        public bool NeuesModell(Model.Modell modell)
        {
            if (modell != null)
            {
                modell.Id = this.modelle.NextId;
                return this.Neu("modelle", this.CreateModellNode(modell));
            }

            return false;
        }

        public bool NeuesAuto(Model.Auto auto)
        {
            if (auto != null)
            {
                auto.Id = this.autos.NextId;
                return this.Neu("autos", this.CreateAutoNode(auto));
            }

            return false;
        }
        #endregion

        #region Bearbeiten
        public bool BearbeiteHersteller(int herstellerId, Model.Hersteller hersteller)
        {
            XmlNode herstellerNode = this.GetNodeById("herstellerroot", herstellerId);

            if (herstellerNode != null && hersteller != null)
            {
                try
                {
                    this.EditAttribute(herstellerNode, "name", (hersteller.Name == null) ? null : hersteller.Name);

                    return this.Speichern();
                }
                catch { }
            }

            return false;
        }

        public bool BearbeiteModell(int modellId, Model.Modell modell)
        {
            XmlNode modellNode = this.GetNodeById("modelle", modellId);

            if (modellNode != null && modell != null)
            {
                try
                {
                    this.EditAttribute(modellNode, "hersteller-id", (modell.Hersteller == null) ? null : modell.Hersteller.Id.ToString(CultureInfo.InvariantCulture));
                    this.EditAttribute(modellNode, "bezeichnung", (modell.Bezeichnung == null) ? null : modell.Bezeichnung);

                    return this.Speichern();
                }
                catch { }
            }

            return false;
        }

        public bool BearbeiteAuto(int autoId, Model.Auto auto)
        {
            XmlNode autoNode = this.GetNodeById("autos", autoId);

            if (autoNode != null && auto != null)
            {
                try
                {
                    this.EditAttribute(autoNode, "modell-id", (auto.Modell == null) ? null : auto.Modell.Id.ToString(CultureInfo.InvariantCulture));
                    this.EditAttribute(autoNode, "kraftstoff", (auto.Kraftstoff == null) ? null : auto.Kraftstoff);
                    this.EditAttribute(autoNode, "leistung", auto.Leistung.ToString(CultureInfo.InvariantCulture));
                    this.EditAttribute(autoNode, "hubraum", auto.Hubraum.ToString(CultureInfo.InvariantCulture));
                    this.EditAttribute(autoNode, "zylinder", auto.Zylinder.ToString(CultureInfo.InvariantCulture));
                    this.EditAttribute(autoNode, "verbrauch-innerorts", (auto.Verbrauch == null) ? null : auto.Verbrauch.Innerorts.ToString(CultureInfo.InvariantCulture));
                    this.EditAttribute(autoNode, "verbrauch-ausserorts", (auto.Verbrauch == null) ? null : auto.Verbrauch.Ausserorts.ToString(CultureInfo.InvariantCulture));
                    this.EditAttribute(autoNode, "farbe", ColorTranslator.ToHtml(auto.Farbe));

                    return this.Speichern();
                }
                catch { }
            }

            return false;
        }

        private void EditAttribute(XmlNode node, string attributeName, object value)
        {
            XmlAttribute attribute = node.Attributes[attributeName];
            if (value != null)
            {
                if (attribute == null)
                {
                    node.Attributes.Append(this.CreateAttribute(attributeName, value));
                }
                else
                {
                    attribute.Value = value.ToString();
                }
            }
            else
            {
                node.Attributes.Remove(attribute);
            }
        }
        #endregion

        #region Löschen
        public bool LoescheHersteller(int herstellerId)
        {
            Model.Modell[] modelle = this.LadeModelle();
            bool b = this.Loesche("herstellerroot", herstellerId);

            if (b)
            {
                foreach (Model.Modell modell in modelle)
                {
                    if (modell.Hersteller != null && modell.Hersteller.Id.Equals(herstellerId))
                    {
                        modell.Hersteller = null;
                        this.BearbeiteModell(modell.Id, modell);
                    }
                }
            }

            return b;
        }

        public bool LoescheModell(int modellId)
        {
            Model.Auto[] autos = this.LoadAutos();
            bool b = this.Loesche("modelle", modellId);

            if (b)
            {
                foreach (Model.Auto auto in autos)
                {
                    if (auto.Modell != null && auto.Modell.Id.Equals(modellId))
                    {
                        auto.Modell = null;
                        this.BearbeiteAuto(auto.Id, auto);
                    }
                }
            }

            return b;
        }

        public bool LoescheAuto(int autoId)
        {
            return this.Loesche("autos", autoId);
        }
        #endregion
        #endregion
    }
}
